package com.netx.generics.io;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import com.netx.generics.basic.Checker;
import com.netx.generics.basic.IntegrityException;
import com.netx.generics.basic.IllegalUsageException;
import com.netx.generics.basic.ObjectAlreadyExistsException;
import com.netx.generics.basic.ObjectNotFoundException;
import com.netx.generics.io.SearchOptions.ORDER;


class DirectoryImpl extends FileObjectImpl implements Directory {

	public DirectoryImpl(FileSystem fs, ProtocolImpl provider) {
		super(fs, provider);
	}

	public int compareTo(Object o) {
		Checker.checkNull(o, "o");
		Directory dir = (Directory)o;
		// Compare contents:
		try {
			SearchOptions so = new SearchOptions((FilenameFilter)null, true, ORDER.NAME_ASCENDING);
			File[] files = getFiles(so);
			File[] oFiles = dir.getFiles(so);
			Directory[] dirs = getDirectories(so);
			Directory[] oDirs = dir.getDirectories(so);
			if((files.length==oFiles.length) && (dirs.length==oDirs.length)) {
				// compare files:
				for(int i=0; i<files.length; i++) {
					if(!files[i].equals(oFiles[i])) {
						return -1;
					}
				}
				// compare directories:
				for(int i=0; i<dirs.length; i++) {
					if(!dirs[i].equals(oDirs[i])) {
						return -1;
					}
				}
				return 0;
			}
			else {
				return -1;
			}
		}
		catch(IOException io) {
			//TODO what to do here??
			throw new IntegrityException(io);
		}
	}
	
	public long getSize() throws IOException {
		return getSize(false);
	}
	
	public long getSize(boolean expanded) throws IOException {
		File allFiles[] = getFiles();
		long size = 0;
		for(int i=0; i<allFiles.length; i++) {
			size += allFiles[i].getSize();
		}
		if(expanded) {
			Directory allDirs[] = getDirectories();
			for(int i=0; i<allDirs.length; i++) {
				size += allDirs[i].getSize(true);
			}
		}
		return size;
	}
	
	public void delete(boolean force) throws IOException {
		if(force) {
			File allFiles[] = getFiles();
			for(int i=0; i<allFiles.length; i++) {
				allFiles[i].delete();
			}
			Directory allDirs[] = getDirectories();
			for(int i=0; i<allDirs.length; i++) {
				allDirs[i].delete(true);
			}
		}
		try {
			provider.delete();
		}
		catch(IOException io) {
			throw provider.translate(io, getPath());
		}
		catch(RuntimeException re) {
			throw provider.translate(re, getPath());
		}
	}

	public void copyTo(Directory dir, SearchOptions options) throws IOException {
		copyTo(dir, getName(), options);
	}

	public void copyTo(Directory dir, String name) throws IOException {
		copyTo(dir, name, null);
	}

	public void copyTo(Directory dir, String name, SearchOptions options) throws IOException {
		Directory newMe = dir.getDirectory(name);
		if(newMe == null) {
			newMe = dir.mkdirs(name);
		}
		File allFiles[] = getFiles(options);
		for(int i=0; i<allFiles.length; i++) {
			allFiles[i].copyTo(newMe);
		}
		Directory allDirs[] = getDirectories(options);
		for (int i=0; i<allDirs.length; i++) {
			allDirs[i].copyTo(newMe);
		}
	}
	
	public boolean exists(String path) throws IOException {
		try {
			return provider.getChild(path).exists();
		}
		catch(IOException io) {
			throw provider.translate(io, getPath());
		}
		catch(RuntimeException re) {
			throw provider.translate(re, getPath());
		}
	}

	public boolean isFile(String path) throws IOException {
		try {
			return provider.getChild(path).isFile();
		}
		catch(IOException io) {
			throw provider.translate(io, getPath());
		}
		catch(RuntimeException re) {
			throw provider.translate(re, getPath());
		}
	}

	public boolean isDirectory(String path) throws IOException {
		try {
			return provider.getChild(path).isDirectory();
		}
		catch(IOException io) {
			throw provider.translate(io, getPath());
		}
		catch(RuntimeException re) {
			throw provider.translate(re, getPath());
		}
	}

	public boolean canRead(String path) throws IOException {
		try {
			return provider.getChild(path).canRead();
		}
		catch(IOException io) {
			throw provider.translate(io, getPath());
		}
		catch(RuntimeException re) {
			throw provider.translate(re, getPath());
		}
	}

	public boolean canWrite(String path) throws IOException {
		try {
			return provider.getChild(path).canWrite();
		}
		catch(IOException io) {
			throw provider.translate(io, getPath());
		}
		catch(RuntimeException re) {
			throw provider.translate(re, getPath());
		}
	}
	
	public File getFile(String path) throws IOException {
		return getFile(path, false);
	}

	public File getFile(String path, boolean create) throws IOException {
		ProtocolImpl child = provider.getChild(path);
		boolean createFile = false;
		try {
			if(child.exists() && child.isFile()) {
				return new FileImpl(getFileSystem(), child);
			}
			else {
				if(create) {
					if(child.exists() && child.isDirectory()) {
						throw new IllegalUsageException("'"+path+"' is a directory");
					}
					else {
						createFile = true;
					}
				}
				else {
					return null;
				}
			}
		}
		catch(IOException io) {
			throw provider.translate(io, path);
		}
		catch(RuntimeException re) {
			throw provider.translate(re, path);
		}
		// This method must be called separately, so that 
		// thrown IOException (which are already translated)
		// do not get re-processed:
		if(createFile) {
			return createFile(path);
		}
		else {
			// not possible:
			throw new IntegrityException();
		}
	}

	public Directory getDirectory(String path) throws IOException {
		return getDirectory(path, false);
	}

	public Directory getDirectory(String path, boolean mkdirs) throws IOException {
		ProtocolImpl child = provider.getChild(path);
		boolean makeDirs = false;
		try {
			if(child.exists() && child.isDirectory()) {
				return new DirectoryImpl(getFileSystem(), child);
			}
			else {
				if(mkdirs) {
					if(child.exists() && child.isFile()) {
						throw new IllegalUsageException("'"+path+"' is a file");
					}
					else {
						makeDirs = true;
					}
				}
				else {
					return null;
				}
			}
		}
		catch(IOException io) {
			throw provider.translate(io, path);
		}
		catch(RuntimeException re) {
			throw provider.translate(re, path);
		}
		// This method must be called separately, so that 
		// thrown IOException (which are already translated)
		// do not get re-processed:
		if(makeDirs) {
			return mkdirs(path);
		}
		else {
			// not possible:
			throw new IntegrityException();
		}
	}

	public File createFile(String path, boolean force) throws IOException {
		ProtocolImpl child = provider.getChild(path);
		//System.out.println("HERE1:child: "+child.getPhysicalPath());
		try {
			if(child.exists()) {
				throw new ObjectAlreadyExistsException("'"+path+"' already exists");
			}
			// Create parent dir:
			ProtocolImpl parent = null;
			if(path.indexOf("/") != -1) {
				parent = child.getParent();
				//System.out.println("HERE2:parent: "+parent.getPhysicalPath());
			}
			else {
				parent = provider;
			}
			if(!parent.exists()) {
				if(force) {
					parent.mkdirs();
				}
				else {
					throw new ObjectNotFoundException("in '"+getPhysicalPath()+"': directory '"+path+"' does not exist");
				}
			}
			// Create file:
			if(!child.createNewFile()) {
				throw new IntegrityException();
			}
		}
		catch(IOException io) {
			throw provider.translate(io, path);
		}
		catch(RuntimeException re) {
			throw provider.translate(re, path);
		}
		return new FileImpl(getFileSystem(), child);
	}
	
	public File createFile(String path) throws IOException {
		return createFile(path, false);
	}

	public Directory mkdirs(String path) throws IOException {
		ProtocolImpl child = provider.getChild(path);
		try {
			if(child.exists()) {
				if(child.isDirectory()) {
					throw new ObjectAlreadyExistsException("'"+path+"': directory already exists");
				}
				else {
					throw new ObjectAlreadyExistsException("'"+path+"': path already exists and is a file");
				}
			}
			if(!child.mkdirs()) {
				// Empirical assumption: we can't create the directory because
				// we don't have the appropriate permissions.
				// (this happens, at least, in windows network paths)
				throw new AccessDeniedException("cound not create '"+path+"'");
			}
		}
		catch(IOException io) {
			throw provider.translate(io, path);
		}
		catch(RuntimeException re) {
			throw provider.translate(re, path);
		}
		return new DirectoryImpl(getFileSystem(), child);
	}

	public String[] listFiles() throws IOException {
		return listFiles(null);
	}

	public String[] listFiles(SearchOptions options) throws IOException {
		String all[] = listAll(options);
		ArrayList<String> files = new ArrayList<String>();
		for(int i=0; i<all.length; i++) {
			if(isFile(all[i])) {
				files.add(all[i]);
			}
		}
		return (String[])files.toArray(new String[0]);
	}

	public String[] listDirectories() throws IOException {
		return listDirectories(null);
	}

	public String[] listDirectories(SearchOptions options) throws IOException {
		String all[] = listAll(options);
		ArrayList<String> dirs = new ArrayList<String>();
		for(String dir : all) {
			if(isDirectory(dir)) {
				dirs.add(dir);
			}
		}
		return (String[])dirs.toArray(new String[0]);
	}

	public String[] listAll() throws IOException {
		return listAll(null);
	}

	public String[] listAll(SearchOptions options) throws IOException {
		String[] all = provider.list();
		// this may happen, if the directory path's length is over 257 chars:
		if(all == null) {
			return new String[0];
		}
		else {
			if(options == null) {
				return all;
			}
			else {
				ArrayList<String> list = new ArrayList<String>();
				for(int i=0; i<all.length; i++) {
					if(getFileSystem().getFilenameFilter() != null && !getFileSystem().getFilenameFilter().accept(all[i])) {
						continue;
					}
					if(options.filter != null && !options.filter.accept(all[i])) {
						continue;
					}
					if(options.showHidden == false && provider.getChild(all[i]).isHidden()) {
						continue;
					}
					list.add(all[i]);
				}
				// order:
				if(options.order != ORDER.NO_ORDER) {
					Collections.sort(list, new StringComparator(options.order));
				}
				return (String[])list.toArray(new String[0]);
			}
		}
	}
	
	public File[] getFiles() throws IOException {
		return getFiles(null);
	}

	public File[] getFiles(SearchOptions options) throws IOException {
		String fileNames[] = listFiles(options);
		File files[] = new FileImpl[fileNames.length];
		for(int i=0; i<fileNames.length; i++) {
			files[i]= getFile(fileNames[i]);
		}
		return files;
	}
	
	public Directory[] getDirectories() throws IOException {
		return getDirectories(null);
	}

	public Directory[] getDirectories(SearchOptions options) throws IOException {
		String dirNames[] = listDirectories(options);
		Directory dirs[] = new DirectoryImpl[dirNames.length];
		for(int i=0; i<dirNames.length; i++) {
			dirs[i] = getDirectory(dirNames[i]);
		}
		return dirs;
	}

	public FileObject[] getAll() throws IOException {
		return getAll(null);
	}

	public FileObject[] getAll(SearchOptions options) throws IOException {
		String allObj[] = listAll(options);
		FileObject obj[] = new FileObject[allObj.length];
		for(int i=0; i<allObj.length; i++) {
			if(isFile(allObj[i])) {
				obj[i] =  getFile(allObj[i]);
			}
			else {
				obj[i] = getDirectory(allObj[i]);
			}
		}
		return obj;
	}
	
	public String[] searchFile(String name) throws IOException {
		return searchFile(name, true);
	}
	
	public String[] searchFile(String name, boolean showHidden) throws IOException {
		SearchOptions options = new SearchOptions(new StringFilenameFilter(name), showHidden);
		return searchFile(options);
	}

	public String[] searchFile(SearchOptions options) throws IOException {
		List<String> list = new ArrayList<String>();
		_search(this, options, _FILE, list, "");
		return (String[])list.toArray(new String[0]);
	}

	public String[] searchDirectory(String name) throws IOException {
		return searchDirectory(name, true);
	}

	public String[] searchDirectory(String name, boolean showHidden) throws IOException {
		SearchOptions options = new SearchOptions(new StringFilenameFilter(name), showHidden);
		return searchDirectory(options);
	}

	public String[] searchDirectory(SearchOptions options) throws IOException {
		List<String> list = new ArrayList<String>();
		_search(this, options, _DIRECTORY, list, "");
		return (String[])list.toArray(new String[0]);
	}

	public String[] searchAll(String name) throws IOException {
		return searchAll(name, true);
	}

	public String[] searchAll(String name, boolean showHidden) throws IOException {
		SearchOptions options = new SearchOptions(new StringFilenameFilter(name), showHidden);
		return searchAll(options);
	}

	public String[] searchAll(SearchOptions options) throws IOException {
		List<String> list = new ArrayList<String>();
		_search(this, options, _ALL, list, "");
		return (String[])list.toArray(new String[0]);
	}
	
	public String toString() {
		return getName();
	}
	
	private byte _FILE = 1;
	private byte _DIRECTORY = 2;
	private byte _ALL = 3;
	
	private void _search(Directory dir, SearchOptions options, byte objectType, List<String> list, String previousPath) throws IOException{
		// search files:
		String objs[] = null;
		if(objectType == _FILE) {
			objs = dir.listFiles(options);
		}
		else if(objectType == _DIRECTORY) {
			objs = dir.listDirectories(options);
		}
		else if(objectType == _ALL) {
			objs = dir.listAll(options);
		}
		else {
			throw new IntegrityException(objectType);
		}
		previousPath = previousPath.equals("") ? "" : previousPath+"/";
		for(Object o : objs){
			list.add(previousPath+o);
		}
		// recursive:
		Directory dirs[] = dir.getDirectories();
		for(Directory d : dirs) {
			_search(d, options, objectType, list, previousPath+d.getName());
		}
	}

	private class StringComparator implements Comparator<String> {
		
		private final byte _order;
		
		public StringComparator(ORDER order) {
			if(order == ORDER.NAME_ASCENDING) {
				_order = 1;
			}
			else if(order == ORDER.NAME_DESCENDING) {
				_order = -1;
			}
			else {
				throw new IntegrityException(order);
			}
		}
		
		public int compare(String a, String b) {
			return a.compareTo(b) * _order;
		}
	}
	
	private class StringFilenameFilter implements FilenameFilter {
		
		private final String _name;
		
		StringFilenameFilter(String filter) {
			_name = filter;
		}

		public boolean accept(String name) {
			return _name.equals(name);
		}
	}
}
